#include "CPerlCounter.h"

// Description: Perl counting and comparison
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     5/30/09  VN      Added .pm as a file extension for Perl

CPerlCounter::CPerlCounter()
{
	classtype = PERL;
	language_name = "Perl";

	file_extension.push_back(".pl");
	file_extension.push_back(".pm");

	LineCommentStart.push_back("#");

	QuoteStart = "\"'";
	QuoteEnd = "\"'";
	QuoteEscapeRear = '\"';

	directive.push_back("use");
	directive.push_back("require");
	directive.push_back("package");
	directive.push_back("import");
	directive.push_back("no");
	//directive.push_back("do");

	data_name_list.push_back("NULL");
	data_name_list.push_back("AUTOLOAD");
	data_name_list.push_back("BEGIN");
	data_name_list.push_back("CORE");
	data_name_list.push_back("DESTROY");
	data_name_list.push_back("END");
	data_name_list.push_back("EQ");
	data_name_list.push_back("GE");
	data_name_list.push_back("GT");
	data_name_list.push_back("INIT");
	data_name_list.push_back("LE");
	data_name_list.push_back("LT");
	data_name_list.push_back("NE");
	data_name_list.push_back("CHECK");

	exec_name_list.push_back("if");
	exec_name_list.push_back("elsif");
	exec_name_list.push_back("until");	
	exec_name_list.push_back("for");
	exec_name_list.push_back("while");
	exec_name_list.push_back("unless");
	exec_name_list.push_back("foreach");
	exec_name_list.push_back("try");
	exec_name_list.push_back("catch");
	exec_name_list.push_back("switch");
	exec_name_list.push_back("or");
	exec_name_list.push_back("eval");
	exec_name_list.push_back("sub");

	math_func_list.push_back("abs");
	math_func_list.push_back("atan2");
	math_func_list.push_back("cos");
	math_func_list.push_back("exp");
	math_func_list.push_back("int");
	math_func_list.push_back("log");
	math_func_list.push_back("rand");
	math_func_list.push_back("sin");
	math_func_list.push_back("sqrt");
	math_func_list.push_back("srand");
	math_func_list.push_back("time");
	math_func_list.push_back("cosh");
	math_func_list.push_back("sinh");
	math_func_list.push_back("tan");
	math_func_list.push_back("tanh");
	math_func_list.push_back("acos");
	math_func_list.push_back("acosh");
	math_func_list.push_back("asinh");
	math_func_list.push_back("atanh");
	math_func_list.push_back("asin");
	math_func_list.push_back("atan");
	math_func_list.push_back("atan2");

	trig_func_list.push_back("cos");
	trig_func_list.push_back("cosh");
	trig_func_list.push_back("sin");
	trig_func_list.push_back("sinh");
	trig_func_list.push_back("tan");
	trig_func_list.push_back("tanh");
	trig_func_list.push_back("acos");
	trig_func_list.push_back("acosh");
	trig_func_list.push_back("asinh");
	trig_func_list.push_back("atanh");
	trig_func_list.push_back("asin");
	trig_func_list.push_back("atan");
	trig_func_list.push_back("atan2");

	log_func_list.push_back("log");

	cmplx_calc_list.push_back("%");
	cmplx_calc_list.push_back("^");
	cmplx_calc_list.push_back("++");
	cmplx_calc_list.push_back("+");
	cmplx_calc_list.push_back("--");
	cmplx_calc_list.push_back("-");
	cmplx_calc_list.push_back("*");
	cmplx_calc_list.push_back("/");
	cmplx_calc_list.push_back(">>");
	cmplx_calc_list.push_back("<<");

	cmplx_cond_list.push_back("elsif");
	cmplx_cond_list.push_back("if");
	cmplx_cond_list.push_back("until");
	cmplx_cond_list.push_back("for");
	cmplx_cond_list.push_back("while");
	cmplx_cond_list.push_back("unless");
	cmplx_cond_list.push_back("foreach");
	
	cmplx_logic_list.push_back("&&");
	cmplx_logic_list.push_back("||");
	cmplx_logic_list.push_back("==");
	cmplx_logic_list.push_back("<=>");
	cmplx_logic_list.push_back("!"); 
	cmplx_logic_list.push_back("and"); 
	cmplx_logic_list.push_back("not"); 
	cmplx_logic_list.push_back("or"); 
	cmplx_logic_list.push_back("xor"); 
	cmplx_logic_list.push_back("~"); 
	cmplx_logic_list.push_back(">");
	cmplx_logic_list.push_back("<");
	cmplx_logic_list.push_back(">=");
	cmplx_logic_list.push_back("=<");
	cmplx_logic_list.push_back("lt");
	cmplx_logic_list.push_back("gt");
	cmplx_logic_list.push_back("ge");
	cmplx_logic_list.push_back("le");
	cmplx_logic_list.push_back("eq");
	cmplx_logic_list.push_back("ne");
	cmplx_logic_list.push_back("cmp");

	cmplx_preproc_list.push_back("use");
	cmplx_preproc_list.push_back("require");
	cmplx_preproc_list.push_back("package");
	cmplx_preproc_list.push_back("import");
	cmplx_preproc_list.push_back("no");

	cmplx_assign_list.push_back("=");
}

int CPerlCounter::CountDirectiveSLOC(filemap* fmap, results* result, filemap* fmapBak)
{
//	bool contd = false;
	if (classtype == DATAFILE) return 0;
//	int cnt = 0;
	string exclude = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$/\\<>.+?!@#$%^&*()-+*";

	for (filemap::iterator iter = fmap->begin(); iter!=fmap->end(); iter++)
	{
		if (CUtil::CheckBlank(iter->line)) continue;

		int cnt=0;
		CUtil::CountTally(" " + iter->line, directive, cnt, 1, exclude, "", "", &directive_count, result->duplicate);
		if (cnt != 0)
		{
			result->directive_lines[PHY]+=cnt;
			result->directive_lines[LOG]+=cnt;
		}
 	}
	return 1;
}

int CPerlCounter::LanguageSpecificProcess(filemap* fmap, results* result, filemap* fmapBak)
{
	string strLSLOC;
	string strLSLOCBak;
	int l_paren_cnt = 0;
	bool l_forflag, found_forifwhile, found_while;
	char prev_char = 0;
	l_forflag = found_forifwhile = found_while = false;

	bool blank_flag	= false;
//	bool data_continue= false; //Mod042909AS
//	bool SLOC_end_flag= false;
//	int num_data_terminators=0;
//	int num_exec_terminators=0;
	bool comment = false;
	int cnt = 0;
	size_t comPos = string::npos;
	size_t p;
	filemap::iterator fit, fitbak;
	string line, lineBak;
	string  exclude = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
	string	special = "[]()+/-*<>=,@&~!^?:%{}";
	vector<string> keywords;
	keywords.push_back("if");
	keywords.push_back("elsif");
	keywords.push_back("until");
	//don't include do in this list
	//don't include for in this list
	keywords.push_back("while");
	keywords.push_back("unless");
	keywords.push_back("foreach");
	keywords.push_back("try");
	keywords.push_back("catch");
	keywords.push_back("switch");
	keywords.push_back("or");
	keywords.push_back("eval");
	keywords.push_back("sub");

	//special case 2;
	vector<string> forKey;
	forKey.push_back("for");
	vector<string> doKey;
	doKey.push_back("do");

	for (fit = fmap->begin(), fitbak = fmapBak->begin(); fit != fmap->end(); fit++, fitbak++)
	{
		// insert blank at the beginning(for searching keywords)
		line = ' ' + fit->line;
		lineBak = ' ' + fitbak->line;

		if (comment==true)
		{
			p = line.find("\n");
			if (p!=string::npos)
			{
				line.replace(0,p+2," ");
				lineBak.replace(0,p+2," ");
				comment = false;
			}
			else continue;
		}

		comPos=line.find("#");
		if (comPos==string::npos)
		{
			comment = false;
			comPos=string::npos;
		}
		else
		{
			comment = true;
			p = line.substr(comPos+3).find("");
			if (p!=string::npos)
			{
				line.replace(comPos,p-comPos+1," ");
				lineBak.replace(comPos,p-comPos+1," ");
				comment = false;
			}
			else
			{
				line.replace(comPos,line.size()-comPos," ");
				lineBak.replace(comPos,lineBak.size()-comPos," ");
			}
		}

		if (CUtil::CheckBlank(line))
		{
			blank_flag = true;
			continue;
		}
		else
		{
			blank_flag = false;
		}
		cnt=0;
		if (!blank_flag)
		{
			//team 12: LSLOC calling
			LSLOC(result, line, lineBak, strLSLOC, strLSLOCBak, l_paren_cnt, l_forflag, found_forifwhile, found_while, prev_char);
			//team 12: end LSLOC calling

			int keywords_count = 0;
//			int semi_count = 0;
			//reject line having compiler directives which is counted earlier(compiler directives check)
			int cnt = 0;
			CUtil::CountTally(line,directive,cnt,1, exclude, "", " ",NULL, result->duplicate, true);
			if (cnt > 0)
			{
				continue;
			}
			//update physical line count
			result->exec_lines[PHY]+=1;
			//counting starts
			//check special case 1 i.e. checks for while();,until();,for( ; ; );		
			int emptyloop_count = 0;
			CUtil::CountTally(line,forKey,emptyloop_count,1, exclude, "", "",NULL, result->duplicate, true);

			bool found_flag = false;
			if (emptyloop_count != 0)
			{
				int brace_count = 0;
				for (size_t i=0;i<line.size();i++)
				{
					if (line[i] == '(')
						brace_count++;
					else if (line[i] == ')')
						brace_count--;
					else if (line[i] == ';')
					{
						if (brace_count == 0)
						{
							found_flag = true;
							break;
						}
					}
				}
			}
			if (found_flag == true)
			{
				keywords_count -= 1;
			}
			//check special case 2 for example any for( ; ; ) should be counted as 1 not 3(because of two semicolons inside the for loop)
			if (found_flag == false)
			{
				int for_count = 0;
				CUtil::CountTally(line,forKey,for_count,1, exclude, "","",NULL, result->duplicate, true);
				if (for_count > 0)
				{
					keywords_count -= for_count;
				}
			}
			//check for do-while loop
			int do_count=0;
			CUtil::CountTally(line,doKey,do_count,1, exclude, "","",NULL, result->duplicate, true);
			if (do_count != 0)
			{
				bool do_directive_flag = false;
				line = CUtil::TrimString(line);
				string::size_type position = line.find("do");
				for (size_t i=position+2;i<line.size();i++)
				{
					if (line[i] != ' ' || line[i] != '\t' || line[i] != '{')
					{
						do_directive_flag = true;
						break;
					}
				}
				if (do_directive_flag == false)
				{
					keywords_count-=1;
				}
			}

			//check pushed list
			CUtil::CountTally(line, keywords,keywords_count,1, exclude, "", " ",NULL, result->duplicate, true);
			CUtil::CountTally(line, exec_name_list, cnt, 1, exclude, "", "", &exec_name_count, result->duplicate);
			CUtil::CountTally(line, data_name_list, cnt, 1, exclude, "", "", &data_name_count, result->duplicate);

/*			//count semicolons
			for (int i=0;i<line.size();i++)
			{
				if (line[i] == ';')
				{
					semi_count++;
				}
			}

			result->exec_lines[LOG]+=semi_count+keywords_count;
			//counting ends */

		}	// end for if
	}	//end for fit
	result->exec_lines[LOG]-= result->directive_lines[LOG];
	return 1;
}

void CPerlCounter::LSLOC(results* result, string line, string lineBak, string &strLSLOC, string &strLSLOCBak, int &paren_cnt,
						 bool &forflag, bool &found_forifwhile, bool &found_while, char &prev_char)
{
	size_t start = 0; //starting index of the working string
	size_t i = 0, strSize;
	bool do_boolean, trunc_flag = false;
	string tmp = CUtil::TrimString(strLSLOC);
	string tmp2;

	// Check for the keyword do
	do_boolean = (CUtil::FindKeyword(tmp, "do") == tmp.length() - 2);

	// We check the entire line for SLOC present in it.
	while (i < line.length())
	{
		if (line[i] == ';' || line[i] == '{')
		{
/*
			LSLOC terminators
			';' Any valid perl command ends with a ';' terminator
			do statements start with a '{' and ends with '}'
*/
			if ((line[i] == ';') && (paren_cnt > 0))
			{	// for a 'for' statement counter is incremented.
				i++;
				continue;
			}

			if (found_while && found_forifwhile)
			{
				found_while = false;
				found_forifwhile = false;
				start = i + 1;
				i++;
				continue;
			}

			if (line[i] == '{')
			{
				// case for(...); and if (...) {
				if (found_forifwhile) // these specials are handled
				{
					found_forifwhile = false;
					start = i + 1;
					i++;
					continue;
				}

				// check if 'do' precedes '{'
				if (!do_boolean)
				{
					// find for 'do' in string before tmp string
					tmp = CUtil::TrimString(line.substr(start, i - start));
					do_boolean = (tmp == "do"); // Check for 'do' statement
				}
				if (do_boolean)
				{
					do_boolean = false;
					start = i + 1;
					i++;
					continue; // do not store '{' following 'do'
				}
			}

			if (line[i] == ';' && prev_char == '}')
			{
				i++;
				continue;
			}

			// The 'for(...)' or 'while(..)' or anything with the '{' on the next line gets counted as an extra SLOC.
			// so to avoid that increment the counter and continue
			if (line[i] == '{' && prev_char == ')')
			{
				i++;
				continue;
			}

			tmp = strLSLOC;
			strSize = CUtil::TruncateLine(i - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
			if (strSize > 0)
			{
				tmp += line.substr(start, strSize);
				strLSLOCBak += lineBak.substr(start, strSize);
			}
			result->addSLOC(strLSLOCBak, trunc_flag);
			result->exec_lines[LOG]++;
			strLSLOC = "";
			strLSLOCBak = "";
			start = i + 1;
		}
		else if(line[i] == '}')
		{
			// Also, {} is also skipped, empty block is not counted
			if (prev_char == ';' || prev_char == '{')
			{
				start = i + 1; 
			}
		}
		else if(line[i] == '(')
		{ 
			if (forflag == 0)
			{
				// handle 'for', 'foreach', 'while', 'if', 'elsif,  and 'unless'
				tmp = "xxx " + CUtil::TrimString(line.substr(start,i));
				if ((CUtil::FindKeyword(tmp, "for") != string::npos) || (CUtil::FindKeyword(tmp, "foreach") != string::npos) ||
					(CUtil::FindKeyword(tmp, "while")!= string::npos) || (CUtil::FindKeyword(tmp, "if") != string::npos) ||
					(CUtil::FindKeyword(tmp, "elsif") != string::npos) || (CUtil::FindKeyword(tmp, "unless") != string::npos))
				{
					forflag = true;
					paren_cnt++;

					if (CUtil::FindKeyword(tmp, "while")!= string::npos) 
						found_while = true;
				}
			}
			else
				paren_cnt++;
		}
		else if(line[i] == ')')
		{
			/* 
			cases 
			'while(...);', 
			'while(...) {' and 
			'} while(...);' 
			is handled in this case
			*/
			if (forflag)
			{
				paren_cnt--;
				if (paren_cnt == 0)
				{
					// handling 'for', 'foreach', 'while', 'if', 'elsif', 'unless'
					tmp = strLSLOC;
					strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
					if (strSize > 0)
					{
						tmp += line.substr(start, strSize);
						strLSLOCBak += lineBak.substr(start, strSize);
					}
					result->addSLOC(strLSLOCBak, trunc_flag);
					result->exec_lines[LOG]++;
					strLSLOC = "";
					strLSLOCBak = "";
					start = i + 1; 
					found_forifwhile = true;
					forflag = false;
				}
			}
		}

		if (line[i] != ' ' && line[i] != '\t')
		{
			// if ;}}} --> don't count }}} at all
			// also, if {}}} --> don't count }}} at all
			if ( !(line[i] == '}' && (prev_char == ';' || prev_char == '{'))) // see case '}' above
				prev_char = line[i];

			if (line[i] != ')' && found_forifwhile) // change to not found if a char appears before
			{
				found_forifwhile = false;
			}

			if (CUtil::FindKeyword(line, "or", i, i + 2, true) == i)
			{
				tmp = strLSLOC;
				strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
				if (strSize > 0)
				{
					tmp += line.substr(start, strSize);
					strLSLOCBak += lineBak.substr(start, strSize);
				}
				result->addSLOC(strLSLOCBak, trunc_flag);
				result->exec_lines[LOG]++;
				strLSLOC = "";
				strLSLOCBak = "";
				start = i;
			}
		}

		i++;
	}

	tmp2 = CUtil::TrimString(line.substr(start, i - start));
	strSize = CUtil::TruncateLine(tmp2.length(), strLSLOC.length(), result->lsloc_truncate, trunc_flag);
	if (strSize > 0)
	{
		strLSLOC += tmp2.substr(0, strSize);
		tmp2 = CUtil::TrimString(lineBak.substr(start, i - start));
		strLSLOCBak += tmp2.substr(0, strSize);
	}

	if (tmp == "")
	{
		found_forifwhile = found_while = false;
	}
}
